<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <script>
        // 一、this指向：this是浏览器在调用函数的时候，会自动传入函数的隐含参数，this永远指向函数执行的上下文对象
        //     注意：this的指向和函数的书写位置无关，只和函数的调用方式有关
        // 根据函数的调用方式不同，this指向分类有以下4种：
        // 1.默认绑定
        // 2.隐式绑定
        // 3.显示绑定
        // 4.new绑定

        // 1.默认绑定：以函数形式调用（函数名（）），this永远指向window
        // 例1：
        // function foo(){
        //     console.log(this);
        //     return function(){
        //         console.log("111",this);
        //     }
        // }
        // let f=foo();//以函数形式调用，默认绑定，this指向window
        // console.log(f);
        // f();//以函数形式调用，默认绑定，this指向window

        // 2.隐式绑定：以方法形式调用，this指向调用该方法的对象（谁调用就指向谁）
        // let obj={
        //     name:"xyz",
        //     fn:function(){
        //         console.log(this);
        //     }
        // }
        // obj.fn();//隐式绑定,this指向obj对象
        // let f=obj.fn;
        // f();//默认绑定，以函数形式调用，this指向window
        // let obj2={
        //     name:"qwe",
        //     foo:obj.fn
        // }
        // obj2.foo();//隐式绑定,this指向obj2

        // 3.显示绑定：用专门的方法改变this指向，apply\call\bind
        // 语法：
        // 函数.apply(改变后this指向的对象，[参数1，参数2....])
        // 函数.call(改变后this指向的对象,参数1，参数2.....)
        // 变量=函数.bind(改变后this指向的对象)；变量是用于接受返回出来的函数，只是这个时候函数的this指向已经被修改过了

        // 注意：使用apply\call\bind三种方法改变this指向时，
        // 如果没有传入参数，或者传入的参数是undefined或者null,this就默认指向window
        // function fn(a,b){
        //     console.log(this);
        // }
        // fn(1,2);//默认绑定，this指向window

        // // 在改变this指向的同时，调用了fn函数
        // // let x=fn.apply({name:'xyz'},[2,3]);//this指向{name:'xyz'}
        // fn.apply();//this指向window
        // fn.apply(undefined);//this指向window
        // fn.apply(null);//this指向window
        // fn.apply(66);//this指向66
        // fn.apply("hello");//this指向"hello"
        // // fn.call({name:"qaz"},4,5);//this指向{name:"qaz"}

        // 这里只是修改fn函数的this指向，并没有调用fn函数
        // let f=fn.bind({name:"zxc"});
        // console.log(f);//函数，函数的this指向已经被bind修改
        // f();//this指向{name:"zxc"}


        // 4.new绑定：使用new关键字，调用构造函数，this就指向该构造函数里面创造出来的对象
        // function Foo(){
        //     this.name="xyz";
        //     this.age=18
        //     console.log(this);
        // }
        // let obj=new Foo();


        // 总结：this绑定的优先级
        // 1.默认绑定优先级最低
        // 2.显示绑定高于隐式绑定
        // 3.new绑定高于隐式绑定
        // 4.new绑定高于bind
        // (1)new绑定和call\apply方法是不允许同时使用的，所以不存在优先级问题
        // (2)new绑定可以和bind同时使用，但是new绑定优先级更高

        // let obj={
        //     name:"xyz",
        //     fn:function(){
        //         console.log(this);
        //     }
        // }
        // // let f=new obj.fn();//this指向fn函数内部产生的空对象{}

        // let x=obj.fn.bind(123);//把fn函数的this指向修改为123
        // // x();
        // let s=new x();//this指向fn函数内部产生的空对象{}


        // 例1：
        // let a={
        //     name:"qwe"
        // }
        // function Test(){
        //     this.name="xyz";
        //     this.fn=function(){
        //         console.log(this.name);//qwe
        //     }.bind(a);
        // }
        // let t=new Test();//实例化对象
        // t.fn();
        // t={
        //     name:"qwe",
        //     fn:function(){
        //         console.log(this.name);
        //     }
        // }


        // 例2：
        // let obj={
        //     name:"qwe",
        //     fn:function(){
        //         console.log(this);
        //     }
        // }
        // let obj2={
        //     name:"xyz",
        //     bar:function(){
        //         console.log(this);
        //     }
        // }
        // let x=obj2.bar=obj.fn;
        // x();//默认绑定，以函数形式调用，this指向window

        // 例3：
        // 注意：setInterval和setTimeout里面的回调函数里面的this永远指向window
        // 除了只用bind修改this指向之外，用于指向window
        // let obj={
        //     name:"xyz",
        //     fn:function(){
        //         console.log(this);
        //     }
        // }

        // let x=obj.fn;
        // setInterval(obj.fn,2000);//this指向window
        // setInterval(x,2000);//this指向window
        // setInterval(obj.fn.bind(123),2000);//this指向123

        // 二、箭头函数的this指向：箭头函数没有自己的this指向，
        // 只能继承上级函数的this指向，如果没有上级函数，就默认指向window对象

        // 注意：1.箭头函数不能作为构造函数
        //      2.箭头函数的this指向不能遵循普通函数的4中this指向绑定规则
        //      3.箭头函数的this指向，之和它上级函数有关，和它本身无关，和调用方式无关
        //      4.箭头函数的上级函数是由箭头函数声明的位置决定的，和调用位置无关
        //      5.箭头函数使用apply\call\bind方法修改this指向无效，因为箭头函数，没有自己的this指向

        // 例1：:
        // let test=(a,b)=>{
        //     console.log(this);
        // }
        // test(1,2);//没有上级函数，this指向window


        // let obj={
        //     name:"xyz",
        //     fn:function(){
        //         let bar=()=>{
        //             console.log(this);//继承上级函数fn的this指向
        //         }
        //         bar();
        //         test(1,2);//this指向window，没有上级函数
        //     }
        // }
        // obj.fn();//以方法形式调用，隐式绑定，this指向obj


        // 例2：
        // let obj2={
        //     name:"qwe",
        //     fn:function(){
        //         console.log(this);//this指向"hello"
        //         let min=()=>{
        //             console.log(this);//this指向"hello"
        //         }
        //         min();
        //     }
        // }
        // obj2.fn.apply("hello");

        // 例3：
        // let obj3 = {
        //     name: "qwe",
        //     fn: () =>{
        //         console.log(this);//this指向window，没有上级函数
        //         let min = () => {
        //             console.log(this); //this指向window，继承上级函数
        //         }
        //         min();
        //     }
        // }
        // obj3.fn.call(123);
        // obj3.fn.apply(123);
        // let b=obj3.fn.bind(123);
        // b();


        // 三、基于对象创建对象：不使用类的方式创建对象，而是对一个已有的对象进行克隆，从而创建一个新的对象
        // 新的对象会完全克隆老的对象所有的属性和方法，老的对象会作为新对象的原型对象存在
        // Object.create()来实现克隆

        // 第一种：完全克隆
        // let oldObj={
        //     name:"小米",
        //     age:18,
        //     height:180,
        //     fn:function(){
        //         console.log(this);
        //     }
        // }
        // let newObj=Object.create(oldObj);//克隆
        // console.log(newObj);
        // console.log(newObj.__proto__===oldObj);//true  老的对象会作为新对象的原型对象存在

        // 第二种：完全克隆，同时新增新对象自己的属性，并设置
        // let oldObj={
        //     name:"小米",
        //     age:18,
        //     height:180,
        //     fn:function(){
        //         console.log(this);
        //     }
        // }
        // let newObj=Object.create(oldObj,{
        //     weight:{
        //         value:120,
        //         writable:false,//writable表示value属性值是否可以修改，false表示不可以修改
        //         enumerable:true //enumerable表示weight属性是否可遍历，true表示可以遍历
        //     },
        //     score:{
        //         value:88,
        //         writable:true,
        //         enumerable:false
        //     }
        // });//克隆

        // console.log(newObj);

        // for(let i in newObj){
        //     console.log(i);
        // }

        // newObj.weight=130;//修改失败，因为该属性值不可修改
        // console.log(newObj);

        // 第三种：克隆继承
        // let oldObj={
        //     name:"小米",
        //     age:18,
        //     height:180,
        //     fn:function(){
        //         console.log(this);
        //     }
        // }
        // let newObj=Object.create(oldObj,{
        //     weight:{
        //         value:120,
        //         writable:false,//writable表示value属性值是否可以修改，false表示不可以修改
        //         enumerable:true //enumerable表示weight属性是否可遍历，true表示可以遍历
        //     },
        //     score:{
        //         value:88,
        //         writable:true,
        //         enumerable:false
        //     }
        // });//克隆
        // let sonObj=Object.create(newObj,{
        //     name:{
        //         value:"son"
        //     },
        //     age:{
        //         value:12
        //     }
        // })
        // console.log(sonObj)

        // 四、混入技术：不需要继承，可以将一个对象的某些属性和方法添加到另一个对象上面，还可以将多个对象混合
        // Object.assign(混合对象,对象1,对象2....)实现混入
        // let a={
        //     name:'a',
        //     age:18
        // }
        // let b={
        //     name:"b",
        //     age:19,
        //     height:180
        // }
        // let c={
        //     name:"c",
        //     age:5,
        //     score:89,
        //     fn:function(){
        //         console.log("hello");
        //     }
        // }
        // Object.assign(a,b,c);
        // console.log(a);
        // {name: 'c', age: 5, height: 180, score: 89, fn: ƒ}
        // 注意：混合对象的时候，相同的属性名，取最后一个对象的属性值

        // 五、深拷贝和浅拷贝：主要是针对复杂数据类型
        // 复杂数据类型：把数据地址存在栈空间，栈空间中的地址又指向堆空间的具体数据
        // 一、浅拷贝：把复杂数据类型的地址赋值给另一个变量，因此其中一个变量修改值，就会影响另一个变量
        // let obj={
        //     name:"小米",
        //     age:19,
        //     height:190
        // }
        // let obj2=obj;
        // obj2.age=29;
        // console.log(obj);
        // console.log(obj2);

        // let arr=[1,2,3,4];
        // let arr2=arr;
        // arr2[2]=333;
        // console.log(arr);
        // console.log(arr2);

        // 二、深拷贝:把复杂数据类型的简单数据类型单独拿出来赋值给另一个变量
        // let obj={
        //     name:"小米",
        //     age:19,
        //     height:190
        // }
        // let obj2={};
        // for(let i in obj){
        //     obj2[i]=obj[i];
        // }
        // obj2.age=29;
        // obj2.height=180;
        // console.log(obj);
        // console.log(obj2);

        // let arr=[4,2,6,12,7,8];
        // let arr2=[];
        // for(let i=0;i<arr.length;i++){
        //     arr2.push(arr[i]);
        // }
        // arr2[3]=3333;
        // console.log(arr);
        // console.log(arr2);

        // let arr = [2, 12, "hello", true, [1, 2, 3], {
        //         test: "xyz",
        //         friends: ["tom", "marry"]
        //     },
        //     [4, [5, 55, 555], 6]
        // ];
        // let arr2 = [];
        // for (let i = 0; i < arr.length; i++) {
        //     if (typeof arr[i] === "object") {
        //         // 判断数据是对象还是数组
        //         if (Array.isArray(arr[i])) {
        //             let data = [];
        //             for (let j = 0; j < arr[i].length; j++) {
        //                 data.push(arr[i][j]);
        //             }
        //             arr2.push(data);

        //         } else {
        //             let data = {};
        //             for(let j in arr[i]){
        //                 data[j]=arr[i][j];
        //             }
        //             arr2.push(data);
        //         }

        //     } else {
        //         arr2.push(arr[i]);
        //     }

        // }
        // arr2[4][1] = 222;
        // arr2[5].test = "qwe";
        // arr2[6][1][1]=10;
        // console.log("arr=", arr);
        // console.log("arr2=", arr2);


        // 深拷贝函数:递归函数
        // const clone = source => {
        //     //判断数据类型是否需要进一步拷贝
        //     if (
        //         typeof (source) === 'object' &&
        //         typeof (source) !== 'function' &&
        //         source !== null
        //     ) {
        //         //根据传入的数据类型，创建一个新的空对象或空数组
        //         let data = Array.isArray(source) ? [] : {};
        //         //遍历原对象或数组
        //         for (let [field, value] of Object.entries(source)) {
        //             //递归拷贝并赋值
        //             data[field] = clone(value)
        //         }
        //         return data
        //     } else {
        //         return source
        //     }
        // }

        // let arr2=clone(arr);
    
        // arr2[4][1] = 222;
        // arr2[5].test = "qwe";
        // arr2[6][1][1]=10;
        // console.log("arr=", arr);
        // console.log("arr2=", arr2);

        // 实现深拷贝的方法：
        // 1.递归函数
        // 2.JSON方法(最简单实现深拷贝的方法)

        // let str=JSON.stringify(arr);//把复杂数据类型转成字符串
        // console.log(str);
        // let arr3=JSON.parse(str);//把字符串转成复杂数据类型
        // arr3[4][1] = 222;
        // arr3[5].test = "qwe";
        // arr3[6][1][1]=10;
        // console.log("arr=", arr);
        // console.log("arr3=", arr3);

        // 六、查看数据类型的方法
        // 1.typeof:只能返回number string boolean undefined object function
        // console.log(typeof 2);//number
        // console.log(typeof "123");//string
        // console.log(typeof true);//boolean
        // console.log(typeof undefined);//undefined
        // console.log(typeof null);//object
        // console.log(typeof {test:"xyz"});//object
        // console.log(typeof [1,2,3]);//object
        // console.log(typeof /^123$/);//object
        // let s=new Set([1,2,3]);
        // console.log(typeof s);//object
        // console.log(typeof function(){});//function


        // 2.JavaScript中最标准的查看数据类型的方法（掌握）
        // Object.prototype.toString.call(数据)

        // console.log(Object.prototype.toString.call(2));//[object Number]
        // console.log(Object.prototype.toString.call("123"));//[object String]
        // console.log(Object.prototype.toString.call(true));//[object Boolean]
        // console.log(Object.prototype.toString.call(undefined));//[object Undefined]
        // console.log(Object.prototype.toString.call(null));//[object Null]
        // console.log(Object.prototype.toString.call({test:"xyz"}));//[object Object]
        // console.log(Object.prototype.toString.call([1,2,3]));//[object Array]
        // console.log(Object.prototype.toString.call(/^123$/));//[object RegExp]
        // let s=new Set([1,2,3]);
        // console.log(Object.prototype.toString.call(s));//[object Set]
        // console.log(Object.prototype.toString.call(function(){}));//[object Function]
        // if(Object.prototype.toString.call([1,2,3])==="[object Array]"){
        //     console.log("是数组~");
        // }

        // 总结：
        // 1.面向对象和面向过程编程范式的区别和优缺点
        // 2.面向对象的三大特征：封装、继承、多态
        // 3.原型链
        // 4.构造函数
        // 5.类和对象的关系
        // 6.es6类的写法
        // 7.es6类继承的写法
        // 8.this指向
        // 9.深拷贝和浅拷贝
        // 10.查看数据类型的方法

        // 面试题：this指向
        var age=19;//全局变量，会成为window对象的属性
        let person1={
            age:88,
            foo1:function(){
                console.log(this.age);
            },
            foo2:()=>{
                console.log(this.age);
            },
            foo3:function(){
                return function(){
                    console.log(this.age);
                }
            },
            foo4:function(){
                return ()=>{
                    console.log(this.age);
                }
            }
        }
        let person2={
            age:66
        }


        // 1.隐式绑定
        person1.foo1();//88
        // 2.显示绑定 foo1函数的this指向改成了person2
        person1.foo1.call(person2);//66
        // 3.箭头函数继承上级函数的this指向，没有上级函数，就默认指向window
        person1.foo2();//19
        // 4.箭头函数使用apply\call\bind方法修改this指向无效，因为箭头函数，没有自己的this指向
        person1.foo2.call(person2);//19
        // 5.返回值是函数，再以函数形式调用，是默认绑定，因此this指向window
        person1.foo3()();//19
        // 相当于：
        // let x=person1.foo3();
        // x();

        // 6.返回值是函数，再以函数形式调用，是默认绑定，因此this指向window
        person1.foo3.call(person2)();//19
        // 相当于：
        // let y=person1.foo3.call(person2);
        // y();

        // 7.返回值是函数，使用call方法修改返回值函数的this指向，this就指向person2
        person1.foo3().call(person2);//66
        // 相当于：
        // let z=person1.foo3();
        // z.call(person2);

        // 8.返回值是箭头函数，继承上级函数的this指向
        // 上级函数是由声明箭头函数的位置决定的，不是调用位置决定的
        // 因此该箭头函数的上级函数是foo4,所以继承foo4函数的this指向
        // foo4是以方式形式调用，是隐式绑定，因此foo4的this指向person1
        person1.foo4()();//88

        // 9.返回值箭头函数的上级函数foo4的this指向被修改为person2
        // 因此箭头函数继承上级函数的this指向，this也指向person2
        person1.foo4.call(person2)();//66

        // 10.返回值是箭头函数，使用call方法修改this指向无效
        // 箭头函数依然继承上级函数foo4的this指向
        // foo4函数是隐式绑定，this指向person1
        person1.foo4().call(person2);//88


    



    </script>
</body>

</html>